package edu.ubc.javascript;

import static edu.ubc.javascript.ASTFactory.assign;
import static edu.ubc.javascript.ASTFactory.call;
import static edu.ubc.javascript.ASTFactory.exprStmt;
import static edu.ubc.javascript.ASTFactory.get;
import static edu.ubc.javascript.ASTFactory.name;
import static edu.ubc.javascript.ASTFactory.newExpr;
import static edu.ubc.javascript.ASTFactory.prop;
import static edu.ubc.javascript.ASTFactory.string;
import static edu.ubc.javascript.ASTFactory.subtract;
import static edu.ubc.javascript.ASTFactory.var;

import java.util.LinkedList;
import java.util.List;

import com.google.javascript.jscomp.mozilla.rhino.Node;
import com.google.javascript.jscomp.mozilla.rhino.ast.AstNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.Block;
import com.google.javascript.jscomp.mozilla.rhino.ast.ExpressionStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.FunctionNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.NodeVisitor;
import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement;

public class ProfilerVisitor implements NodeVisitor {

	private ReflectiveAstTransformer tx;
	
	public ProfilerVisitor(ReflectiveAstTransformer tx) {
		this.tx = tx;
	}
		
	@Override
	public boolean visit(AstNode node) {
		if(node instanceof FunctionNode) {
			FunctionNode fn = (FunctionNode) node;
			String name = fn.getName();
			if(name.equals("")) {
				name = "(anonymous)";
			}
			tx.beforeExecution(fn, makeBeginProfile());
			tx.beforeExecution(fn, this.makePushFunctionName(name));
		}
		if(node instanceof ReturnStatement) {
			tx.insertBefore(node.getParent(), node, this.makeEndProfile());
			tx.insertBefore(node.getParent(), node, this.makePopFunctionName());
		}
		return true;
	}
	
	private List<AstNode> makeBeginProfile() {
		List<AstNode> before = new LinkedList<AstNode>();
		ExpressionStatement exprStmt = exprStmt(var("___start"));
		before.add(exprStmt);
		exprStmt = exprStmt(assign(name("___start"), call(get(newExpr("Date"), "getTime"))));
		before.add(exprStmt);
		return before;
	}
	
	private AstNode makeEndProfile() {
		return exprStmt(call("___profileSample", subtract(call(get(newExpr("Date"), "getTime")), name("___start")) ));
	}
	
	private AstNode makePushFunctionName(String name) {
		ExpressionStatement exprStmt = exprStmt(call("___pushFnName", string(name)));
		return exprStmt;
	}

	private AstNode makePopFunctionName() {
		ExpressionStatement exprStmt = exprStmt(call("___popFnName"));
		return exprStmt;
	}
}
